Ajouve
Ajouve

Reputation: 10089

React context not keeping data in state

I am trying to use React context to replace my actual Redux. I want to use this context to store an array of files. This is a nextjs app, I do not know it can cause some issues

I have my FileContextProvider

import { createContext, useState, useEffect } from "react";

export const FileContext = createContext({});

export function FileContextProvider({ children }) {

    const [files, setFiles] = useState([]);

    useEffect(() => {
        console.log('files', files);
    }, [files])

    const addLocalFiles = (newFiles, mode) => {
        console.log('addLocalFiles', files);
        setFiles([...files].concat(newFiles));
    }

    return (
        <FileContext.Provider value={{
            files,
            addLocalFiles
        }} >
            {children}
        </FileContext.Provider>
    );
}

Then I created a global context where I want to initialize all my context, at the moment I only have the file one.

import { FileContextProvider } from "./file";

export default function Context({ children }) {

    return (
        <FileContextProvider>
            {children}
        </FileContextProvider>
    );

}

Then I add the context in _app.js

import Context from '../context';

function MyApp({ Component, pageProps }) {

  return (
    <Context>
      <Component {...pageProps} />
    </Context>
  )
}

export default MyApp;

In one of my child component I have

import { FileContext } from '../../context/file';

export default function TopBlock({ type, format }) {

    return (
               <div className="col-12 col-lg-8 offset-lg-2">
                   <FileContext.Consumer>
                       {({ files, addLocalFiles, removeFile, updateFile }) => (
                                    <UploadBlock files={files} addLocalFiles={addLocalFiles} />
                       )}
                   </FileContext.Consumer>
               </div>
    );

}

Here my UploadBlock

import React, { useCallback, useRef } from 'react';
import { useDropzone } from 'react-dropzone';

export default function UploadBlock({ files, addLocalFiles }) {

    const dropzoneParent = useRef(null);

    function MyDropzone() {
        const onDrop = useCallback(async acceptedFiles => {
            addLocalFiles(acceptedFiles)
        }, [])

        const dropOptions = { onDrop }
        const { getRootProps, getInputProps, isDragActive } = useDropzone(dropOptions)

        return (
            <div {...getRootProps()} ref={dropzoneParent}>
                <input {...getInputProps()} />
                <div>Drop Here</div>
            </div>
        )
    }

    return (
            <div>
                {MyDropzone()}
            </div>
    )

}

In my UploadBlock when I call addLocalFiles it's working, I have my files variable containing my new files but if I call a second time addLocalFiles the previous files does not exist in FileContextProvider , console.log('addLocalFiles', files); is returning an empty array.

The useEffect is only to debug, it is triggered each time addLocalFiles is called, and new files are well print.

I do not understand why files from the state became an empty array when I am calling back addLocalFiles

I have only one instance of Context and FileContext.Consumer I do not know if it makes any changes

Upvotes: 0

Views: 2210

Answers (2)

Jae
Jae

Reputation: 556

In React, states are dependent on specific render. It means, every render will have its own states. So, by the time addLocalFiles are declared, specific files states are dependent on the render.

const addLocalFiles = (newFiles, mode) => {
  console.log('addLocalFiles', files);
  setFiles([...files].concat(newFiles)); // state `files` are used
}

I think the problem could be that you are wrapping addLocalFiles function with useCallback.

const onDrop = useCallback(async acceptedFiles => {
  addLocalFiles(acceptedFiles)
}, []) // empty dependency, so it will always referencing same `addLocalFiles` function.

addLocalFiles will never be generated again, even if it renders. So, changed states files are not going to be applied in addLocalFiles. If you want to prevent useless re-render, you should specifying its dependency, files.

const onDrop = useCallback(async acceptedFiles => {             
  addLocalFiles(acceptedFiles, mode)
}, [addLocalFiles]); // this function is dependent on `files`

Or you can just skip useCallback, if optimization is not really necessary. I found a StackOverflow question that could help you better understading.

Upvotes: 1

dada abiola
dada abiola

Reputation: 1

please provide your component so we can be sure how u call the addLocalFiles method with the required parameter. i also think there is no need for spreading [...files].concat(newfile) instead of files.concat(newfile)

Upvotes: 0

Related Questions