React - context not updating - reactjs

I am learning React by taking the implementation of reading a csv file, and breaking it into separate components that share data with context.
The problem is that after the user selects a file with FileSelector component, the state in ColumnsSelector is not updated, and file object (in ColumnSelector component) is still an empty object.
Thanks
Code:
FileSelector.jsx component gets the file from the user:
import React, { useContext } from "react";
import { DataContext } from './DataContext'
// Allowed extensions for input file
const allowedExtensions = ["csv"];
const FileSelector = () => {
const newError = useContext(DataContext).newError;
const changeFile = useContext(DataContext).changeFile;
const handleFileChange = (e) => {
if (e.target.files.length) {
const inputFile = e.target.files[0];
const fileExtension = inputFile?.type.split("/")[1];
if (!allowedExtensions.includes(fileExtension)) {
newError("Please input a csv file");
return;
}
changeFile(inputFile);
}
};
return (
<div>
<label htmlFor="csvInput" style={{ display: "block" }}>
Enter CSV File
</label>
<input
onChange={handleFileChange}
id="csvInput"
name="file"
type="File"
/>
</div>
);
};
export default FileSelector;
ColumnsSelector.jxs will be updated after the user selects a file, and will return the file headers:
import React, { useContext } from "react";
import Papa from "papaparse";
import { DataContext } from './DataContext'
// Allowed extensions for input file
const allowedExtensions = ["csv"];
const ColumnsSelector = () => {
const data = useContext(DataContext).data;
const changeData = useContext(DataContext).changeData;
const error = useContext(DataContext).error;
const newError = useContext(DataContext).newError;
const file = useContext(DataContext).file; // object is empty also after user uploads a file!
const handleParse = () => {
// If user clicks the parse button without
// a file we show a error
if (!file) return newError("Enter a valid file");
// Initialize a reader which allows user
// to read any file or blob.
const reader = new FileReader();
// Event listener on reader when the file
// loads, we parse it and set the data.
reader.onload = async ({ target }) => {
const csv = Papa.parse(target.result, { header: true });
const parsedData = csv?.data;
const columns = Object.keys(parsedData[0]);
changeData(columns);
};
reader.readAsText(file);
};
return (
<div>
<div>
<button onClick={handleParse}>Parse</button>
</div>
<div style={{ marginTop: "3rem" }}>
{Object.keys(data).length === 0 ? "" : data.map((col,
idx) => <div key={idx}>{col}</div>)}
</div>
</div>
);
};
export default ColumnsSelector;
DataSelector.jsx is a container for both components
import React, { useState } from "react";
import { DataProvider } from './DataContext'
import FileSelector from './FileSelector';
import ColumnsSelector from './ColumnsSelector'
// Allowed extensions for input file
const allowedExtensions = ["csv"];
const DataSelection = () => {
// This state will store the parsed data
const [data, setData] = useState([]);
// It state will contain the error when
// correct file extension is not used
const [error, setError] = useState(null);
// It will store the file uploaded by the user
const [file, setFile] = useState("");
const providerOptions = {
data: {},
changeData: (value) => setData(value),
error: {},
newError: (value) => setError(value),
file: {},
changeFile: (value) => setFile(value),
}
return (
<div>
<DataProvider value={providerOptions}>
<FileSelector />
<ColumnsSelector/>
</DataProvider>
</div>
);
};
export default DataSelection;
app.js holds DataSelector component:
import React from 'react';
import './App.css';
import DataSelector from './DataSelector';
function App() {
return (
<div className="App">
<header className="App-header">
<div>
<DataSelector />
</div>
</header>
</div>
);
}
export default App;
DataContext.js defined the context:
import React from 'react';
export const DataContext = React.createContext({});
export const DataProvider = DataContext.Provider;
export const DataConsumer = DataContext.Consumer;

Related

Is possible to build compound component with separated files? REACT

I'm trying to understand React's compound pattern. In all exercises all components are in one file. Is it possible to build component with that pattern with external components?
I would achieve that scenario:
src:
components:
Main
Component1
Component2
Component3
// ONE FILE Main.js
import {CompoundComponent1, CompoundComponent2, CompoundComponent3} './foo'
const Main = () => {
const [on, setOn] = React.useState(false)
const toggle = () => setOn(!on)
const CompoundComponent1 = Component1;
const CompoundComponent2 = Component2;
const CompoundComponent3 = Component3;
return <Switch on={on} onClick={toggle} />
}
Main.C1 = CompoundComponent1
Main.C2 = CompoundComponent2
Main.C3 = CompoundComponent3
// ONE FILE END
App.js
const App = () => {
<Main>
<Main.C1>FOO</Main.C1>
// etc.
</Main>
}
I think that i found solution.
import * as React from 'react'
import {Switch} from '../switch'
import {ToggleOn} from './02/ToggleOn'
import {ToggleOff} from './02/ToggleOff'
import {ToggleButton} from './02/ToggleButton'
function Toggle() {
const [on, setOn] = React.useState(false)
const toggle = () => setOn(!on)
return <Switch on={on} onClick={toggle} />
}
Toggle.ToggleOn = ToggleOn
Toggle.ToggleOff = ToggleOff
Toggle.ToggleButton = ToggleButton
function App() {
return (
<div>
<Toggle>
<Toggle.ToggleOn>Turn ON</Toggle.ToggleOn>
<Toggle.ToggleOff>Turn OFF</Toggle.ToggleOff>
<Toggle.ToggleButton />
</Toggle>
</div>
)
}
export default App
In separated files:
export const ToggleButton = ({on, toggle}) => (
<Switch on={on} onClick={toggle} />
)
export const ToggleOn = ({on, children}) => {
if (on) {
return children
}
return null
}

Local Image Upload Error in react-email-editor

I have integrated the react-email-editor into react, and when I upload an image from my local (size less than 500kb) It shows failed to upload image.
Please help, Below is the screen shot.
image
import { useRef, useState } from "react";
import EmailEditor from 'react-email-editor';
import sample from "./sample.json";
function App() {
const emailEditorRef = useRef(null);
const [json,SetJson]=useState();
const exportHtml = () => {
emailEditorRef.current.editor.exportHtml((data) => {
const { design, html } = data;
console.log('exportHtml', html);
console.log('design', design.body);
});
emailEditorRef.current.editor.saveDesign((design) => {
console.log("saveDesign", JSON.stringify(design, null, 4));
alert("Design JSON has been logged in your developer console.");
});
};
const onLoad = () => {
// editor instance is created
// you can load your template here;
const templateJson = sample;
emailEditorRef.current.editor.loadDesign(templateJson);
}
const onReady = () => {
// editor is ready
console.log('onReady');
};
return {
<div>
<div>
<button onClick={exportHtml}>Export HTML</button>
</div>
<div style={{width:"70vw"}}>
<EmailEditor ref={emailEditorRef} onLoad={onLoad} />
</div>
</div>
}
}
export default App;

React Typescript Video Preview

I'm trying to create a video preview for a internal project, with "React & Typescript" using react hooks below is the component code,
import React, { useEffect, useRef, useState } from 'react';
import { INewVideo } from 'src/models';
import { useForm } from 'react-hook-form';
const NewVideo: React.FC = () => {
const { register, handleSubmit } = useForm<INewVideo>();
const [file, setFile] = useState<any>();
const videoChangeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
console.log(event.currentTarget.files![0]);
setFile(event.currentTarget.files![0])
};
useEffect(() => {
console.log("use effect", file)
}, [file])
return (<div>
<input
accept="video/mp4, video/mov"
onChange={videoChangeHandler}
type="file"
/>
{
file ? (
<div>
{file}
</div>
) : ("No Video")
}
</div>)
};
export default NewVideo;
But I'm not able to set the file, its throwing below error
I need to render upload video & give options for screen capture & trimming features. But these are later stages
You are getting this error because file is not a JSX.Element which you are trying to render in your DOM. Basically you got some Object in your file state. Either you can provide this as a source for HTML.Video Element or you can get file object data from it.
{
file ? <div> {file.name}</div> : "No Video";
}
This code should print the file name in your screen. This is the main place where you are getting some error.
Or if you want to show the preview of your recent upload video you can simply pass that file object as a HTML.Video src. Like it:
{
file ? <div> <video src={URL.createObjectURL(file)} autoPlay /></div> : "No Video";
}
This will show the preview of your video.
I've found below
import React, { useEffect, useState } from 'react';
import { INewVideo } from 'src/models';
import { useForm } from 'react-hook-form';
const NewVideo: React.FC = () => {
const { register } = useForm<INewVideo>();
const [file, setFile] = useState<any>();
const videoChangeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.currentTarget.files![0];
console.log("File", file);
const reader = new FileReader();
reader.addEventListener("load", () => {
setFile(reader.result);
});
reader.readAsDataURL(event.target.files![0]);
};
useEffect(() => {
console.log("use effect", file)
}, [file])
return (<div>
<input
{...register("Link")}
accept="video/mp4, video/mov"
onChange={videoChangeHandler}
type="file"
/>
<video controls src={file} />
</div>)
};
export default NewVideo;

passing object using context and doing iteration with map

This is a simple question but I couldn't reach the final result after a lot of attempts. The problem is that I want to pass an object in context and use it in another file. And then do an iteration and create a specific element for each value.
App.jsx
const [activities, setActivity] = useState([
{
key: Math.random() * Math.random(),
name: 'Hello',
}
]);
const inputValue = useRef(null);
const addActivity = () => {
const activity = {
key: Math.random() * Math.random(),
name: inputValue.current.value,
};
setActivity(activities.concat(activity));
};
const value = {
// I want to pass this parameter - only activities has problem (Activity.jsx <h1>)
// I can't achieve activities.name in Activity.jsx
activities: [...activities],
functions: {
addActivity: addActivity
},
ref: {
inputValue: inputValue
}
};
<Context.Provider
value={value}
>
Context.js
export const Context = createContext();
Activity.jsx
const { activities, functions, ref } = useContext(Context);
return (
<section className="activity-container">
<input type="text" ref={ref.inputValue} />
<button onClick={functions.addActivity}>add!</button>
{
activities.map(activity => (
<h1>activity.name</h1>
))
}
</section>
);
I believe this is what you want:
// Sharing data through context
Context file:
// Context.js
import React, { useState, useRef, createContext } from "react";
export const DataContext = createContext();
const getRandom = () => Math.random() * Math.random();
const defaultValue = {
key: getRandom(),
name: "Hello"
};
const ContextProvider = ({ children }) => {
const [activities, setActivity] = useState([defaultValue]);
const inputValue = useRef(null);
const addActivity = () => {
const activity = {
key: getRandom(),
name: inputValue.current.value
};
setActivity([...activities, activity]);
};
const value = {
activities: [...activities],
functions: { addActivity },
ref: { inputValue }
};
return <DataContext.Provider value={value}>{children}</DataContext.Provider>;
};
export default ContextProvider;
Hook to read from context:
// useDataContext
import { useContext } from "react";
import { DataContext } from "./Context";
const useDataContext = () => {
const contextValue = useContext(DataContext);
return contextValue;
};
export default useDataContext;
Child Element where you want to receive the value from context:
// Child.js
import React from "react";
import useDataContext from "./useDataContext";
const Child = () => {
const data = useDataContext();
return (
<>
{data.activities.map((val, idx) => (
<div key={idx}>Name is {val.name}</div>
))}
</>
);
};
export default Child;
And the App container:
// App.js
import Child from "./Child";
import ContextProvider from "./Context";
export default function App() {
return (
<div className="App">
<ContextProvider>
<Child />
</ContextProvider>
</div>
);
}
I've created a sandbox for you to test.
You should make sure that the Activity.jsx component is wrapped with context provider, to get the proper value from the context.
I tried in this codesandbox, and it's working properly. You can refer to this and check what you are missing.

Using Draft js mention plugin with react hooks

I have been trying to get draft js mention plugin to work with react hooks but can't seem to figure what's wrong with the code. Appreciate any help on this.
import React, { useRef, useState, useEffect } from "react";
import { EditorState } from "draft-js";
import Editor from "draft-js-plugins-editor";
import createMentionPlugin, { defaultSuggestionsFilter } from "draft-js-mention-plugin";
import mentions from "./mentions";
export default function MentionEditor() {
const [editorState, setEditorState] = useState(EditorState.createEmpty());
const [suggestions, setSuggestions] = useState(mentions);
const editor = useRef(null);
useEffect(() => {
editor.current.focus();
}, [])
const mentionPlugin = createMentionPlugin();
const { MentionSuggestions } = mentionPlugin;
const plugins = [mentionPlugin];
const onSearchChange = ({ value }) => {
setSuggestions(defaultSuggestionsFilter(value, mentions))
};
return (
<div style={{ border: "1px solid gray" }}>
<Editor
editorState={editorState}
onChange={editorState => setEditorState(editorState)}
plugins={plugins}
ref={editor}
/>
<MentionSuggestions
onSearchChange={onSearchChange}
suggestions={suggestions}
/>
</div>
);
}
You need to move the draft-js plugin configuration outside the component arrow function. This is a pretty basic Draft-JS implementation using a functional component and hooks:
import React, { useState, useRef } from 'react'
import { EditorState } from 'draft-js'
import Editor from 'draft-js-plugins-editor'
import createMentionPlugin, { defaultSuggestionsFilter } from 'draft-js-mention-plugin'
import 'draft-js/dist/Draft.css'
import 'draft-js-mention-plugin/lib/plugin.css'
import mentions from "./mentions"
// Draft-JS-Mentions plugin configuration
const mentionPlugin = createMentionPlugin()
const { MentionSuggestions } = mentionPlugin
const plugins = [mentionPlugin]
const MyEditor= () => {
const [suggestions, setSuggestions] = useState(mentions)
// Draft-JS editor configuration
const [editorState, setEditorState] = useState(
() => EditorState.createEmpty(),
)
const editor = useRef(null)
// Check editor text for mentions
const onSearchChange = ({ value }) => {
setSuggestions(defaultSuggestionsFilter(value, mentions))
}
const onAddMention = () => {
}
// Focus on editor window
const focusEditor = () => {
editor.current.focus()
}
return (
<div onClick={() => focusEditor()}>
<Editor
ref={editor}
editorState={editorState}
plugins={plugins}
onChange={editorState => setEditorState(editorState)}
placeholder={'Type here...'}
/>
<MentionSuggestions
onSearchChange={onSearchChange}
suggestions={suggestions}
onAddMention={onAddMention}
/>
</div>
)
}
export default MyEditor
Just move these lines outside component and it will work:
const mentionPlugin = createMentionPlugin();
const { MentionSuggestions } = mentionPlugin;
const plugins = [mentionPlugin];
export default function MentionEditor() {
const [editorState, setEditorState] = useState(EditorState.createEmpty());
.. ... ...
}
!!!!!!!!!!!!!!!! PAY ATTENTION !!!!!!!!!!!!
The onSearchChange method will be triggered once the '#' character is typed, so in this case it will return just 5 items that fit the empty string...
To prevent this to be happened, just check that the value we want to search is not empty:
const onSearchChange = ({ value }) => {
if (value) {
setSuggestions(defaultSuggestionsFilter(value, mentions));
}
};

Resources