Component not re-rendering with this update of context - json

Short story: I am making my own GIS (geographic information system) and want to be able to upload JSON files with geographical data. I do not however want to save files in a database, just in a list. Furthermore I'm using Context to parse data to the <MAP/> (leaflet) component.
When I upload new JSONs to the layerList it gets updated but the <MAP/> component does not re-render.
I might be using a weird way of updating the state, but I dont know how to do it differently.
Here is my code
import React, {createContext, useState} from 'react';
import "../../App.css";
import data from '../../Layers/layer1.json'
import data1 from '../../Layers/layer2.json'
export const FileContext = createContext()
const layerList = [data]
function updateList(layer){
layerList.push(layer)
}
// Create an object of formData
const onFileChange = e => {
const fileReader = new FileReader();
fileReader.readAsText(e.target.files[0], "UTF-8");
fileReader.onload = e => {
updateList(JSON.parse(e.target.result));
console.log(layerList)
};
//console.log(layerList)
}
export const FileProvider = (props) => {
const [layer, setLayer] = useState(
layerList
)
return(
<FileContext.Provider value = {layer}>
{props.children}
</FileContext.Provider>
);
}
function FileUpload() {
return (
<div>
<div id='fileupload'>
<input type="file" onChange={onFileChange} />
</div>
</div>
);
}
export default FileUpload;

Your layerList exists and is maintained outside of the scope of your component. It's not aware of changes being made to the layerList in this fashion. You need to move your logic a little to look more like this:
import React, {createContext, useState} from 'react';
import "../../App.css";
import data from '../../Layers/layer1.json'
import data1 from '../../Layers/layer2.json'
export const FileContext = createContext()
// Create an object of formData
const onFileChange = e => {
const fileReader = new FileReader();
fileReader.readAsText(e.target.files[0], "UTF-8");
fileReader.onload = e => {
updateList(JSON.parse(e.target.result));
console.log(layerList)
};
//console.log(layerList)
}
export const FileProvider = (props) => {
const [layerList, setLayerList] = useState([data]);
const updateList (layer) => {
setLayerList([layer, ...layerList]); //This creates a new array in memory, so it will trigger a rerender.
};
useEffect(() => {
//Update json file logic
}, [layerList])
return(
<FileContext.Provider value = {layerList, setLayerList}>
{props.children}
</FileContext.Provider>
);
}
function FileUpload() {
//change this component to be a context consumer and the 'onChange' event can fire the `setLayerList` function to update everything.
return (
<div>
<div id='fileupload'>
<input type="file" onChange={onFileChange} />
</div>
</div>
);
}
export default FileUpload;

Related

How to implement multiple API in one component

i stuck in a project where i have to implement JSON Place Holder Post API and JSON Place Holder Comment API both API in a particular component.Actually my task is build a project like a facebook post component where user can post and comment. I implemented Post API successfully but i couldn't find any solution to use comment API. I did all thing but it's not show in my Home component.
How can i implement comment api in my home component
my console said it present but i couldn't show this
This is Home.js File
import React, { useEffect, useState } from 'react';
import Post from '../Post/Post';
import Comment from '../Comment/Comment';
import './Home.css';
const Home = () => {
const [post,setPost] = useState([]);
const [comment,setComment] = useState([]);
useEffect(()=>{
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res=>res.json())
.then(data=>setPost(data))
},[])
useEffect(()=>{
fetch('https://jsonplaceholder.typicode.com/comments')
.then(res=>res.json())
.then(data=>setComment(data))
},[])
return (
<div>
<div>
{
post.map(post=><Post post={post}></Post>)
}
</div>
<div className="main-body">
{
comment.map(comment=><Comment comment={comment}></Comment>)
}
</div>
</div>
);
};
export default Home;
This comment.js File
import React from 'react';
const Comment = (props) => {
const {name,email} = props.comment.name;
console.log(props.comment);
return (
<div>
{name}
{email}
</div>
);
};
export default Comment;
This is post.js File
import React from 'react';
import './Post.css';
const Post = (props) => {
const {title,body} = props.post;
return (
<div className="body-style">
<h1 className="name">{title}</h1>
<p>{body}</p>
</div>
);
};
export default Post;
Please help me I need solution
The structure is incorrect, in order to do that, comment should be children of post, and home will pass data to the post. Since you fetch data from 2 difference API, you need to combined it into 1 source and pass that down.
Home.js
import React, { useEffect, useState } from 'react';
import Post from '../Post/Post';
import './Home.css';
const Home = () => {
const [post,setPost] = useState([]);
const [comment,setComment] = useState([]);
const [ info, setInfo ] = useState([]);
useEffect(()=>{
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res=>res.json())
.then(data=>setPost(data))
},[])
useEffect(()=>{
fetch('https://jsonplaceholder.typicode.com/comments')
.then(res=>res.json())
.then(data=>setComment(data))
},[])
//Function to combine post and comment base on ID
const merge = (post, comment) => {
const temp = [];
post.forEach((x) => {
comment.forEach((y) => {
if (x.id === y.id) {
let cName = y.name;
let cEmail = y.email;
let cBody = y.body;
temp.push({ ...x, cName, cEmail, cBody });
}
});
});
return temp;
};
useEffect(
() => {
setInfo(merge(post, comment));
console.log(info);
},
[ post, comment ]
);
return (
<div>
{info.map((each) => <Post key={each.id} data={each} />)}
</div>
);
};
export default Home;
Post.js
import React from 'react';
import Comment from './Comment';
const Post = (props) => {
const { title, body, cEmail, cName } = props.data;
return (
<div className="body-style">
<h1 className="name">{title}</h1>
<p>{body}</p>
<Comment email={cEmail} name={cName} />
</div>
);
};
export default Post;
Comment.js
import React from 'react';
const Comment = ({ name, email }) => {
return (
<div>
{name}
{email}
</div>
);
};
export default Comment;

How do you loop through json with map and filter in React?

I have data in json format and I want to loop through it to render the same component (ContentThumbnail) eight times but with different titles and other content.
I have tried creating a function which accepts four parameters to achieve this. Here is the function I've written in a separate file called RenderContent.js:
import React from 'react';
import ContentThumbnail from './ContentThumbnail';
function RenderContentThumbnail(data, sectionName, wrapperStart, wrapperEnd) {
return (
<div>
{data
.filter(d => d.sectionName === { sectionName })
.map(filteredSection => (
{wrapperStart}
<ContentThumbnail {filteredSection.title} />
{wrapperEnd}
))}
</div>
);
}
export default RenderContentThumbnail;
And here is where I'm trying to execute that function in my component DefaultDashboard.js:
import React, { useEffect } from 'react';
import RenderContent from '../../content-thumbnail/RenderContent';
const DefaultDashboard = () => {
const { data } = useFetchData({ queryString: `${contentLibraryApiUrl}/GetContentForPage/Home` });
return (
RenderContentThumbnail(data, "topSection", "<div>", "</div>")
);
};
export default DefaultDashboard;
Is anyone able to help me see where I'm going wrong? I'm getting errors inside my map function and the page won't render at all.:(
Many thanks!
Katie
UPDATE!
I have made a tweak to the code to specify the prop, which is called "title", but I'm getting the following:
You should change the way you are rendering RenderContent:
const DefaultDashboard = () => {
const { data } = useFetchData({ queryString: `${contentLibraryApiUrl}/GetContentForPage/Home` });
return (
<RenderContent data={data} sectionName="topSection" wrapperStart="<div>" wrapperEnd= "</div>")
);
};
You can make it a lot easier, removing RenderContentThumbnail:
const DefaultDashboard = () => {
const { data } = useFetchData({ queryString: `${contentLibraryApiUrl}/GetContentForPage/Home` });
return (
{data
.filter(d => d.sectionName === "topSection")
.map(filteredSection => (<div>
<ContentThumbnail title={filteredSection.title} />
</div>))
}
);
};
export default DefaultDashboard;

Object not recognized as JSON object

Short story: I am making my own GIS (geographic information system) and want to be able to upload JSON files with geographical data. I do not however want to save files in a database, just in a list. Furthermore I'm using Context to parse data to the <MAP/> (leaflet) component.
My problem is that when pushing the JSON to the list it is not recogniezed as a JSON but as string. How can I solve this problem.
I am quite new to react so I am open for suggestions to solve it differently.
Here is my code
import React, {createContext, useState} from 'react';
import "../../App.css";
import data from '../../Layers/layer1.json'
import data1 from '../../Layers/layer2.json'
export const FileContext = createContext()
const layerList = []
function updateList(layer){
layerList.push(layer)
}
export const FileProvider = (props) => {
const [layer, setLayer] = useState(
layerList
)
return(
<FileContext.Provider value = {[layer, setLayer]}>
{props.children}
</FileContext.Provider>
);
}
// Create an object of formData
const onFileChange = e => {
const fileReader = new FileReader();
fileReader.readAsText(e.target.files[0], "UTF-8");
fileReader.onload = e => {
console.log(e.target.result);
updateList(e.target.result);
console.log(layerList)
};
//console.log(layerList)
}
function FileUpload() {
return (
<div>
<div id='fileupload'>
<input type="file" onChange={onFileChange} />
</div>
</div>
);
}
export default FileUpload;
And this is the Map component
import { Map, TileLayer, GeoJSON} from 'react-leaflet'
import "../App.css";
import data from '../Layers/layer1.json'
import { FileContext } from '../LandingPage/ToolbarComponents/FileUpload';
function MapOslo() {
const [layer, setLayer] = useContext(FileContext)
return (
<Map center={[59.93, 10.75]} zoom={4}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='© OpenStreetMap contributors'
/>
<GeoJSON data={layer} style={['color','#006400']} />
</Map>
);
}
export default MapOslo;
To convert a String to JSON you can use JSON.parse(str)
var jsonStr = "{test: 'hi'}";
var json = JSON.parse(jsonStr);
So I think you have to implement parse here: updateList(JSON.parse(e.target.result));

In React, is it possible to store a ref in a context?

I need global app-wide access to a VideoElement to play it on user events on browsers like Safari and was wondering if storing the VideoElement in a context would be the best way to do that. I programmatically play my video through a redux action and in Safari that is not possible unless it has been played once through a user triggered event (like a click)
Is it possible to store an element (ref) within a context? The VideoElement will be then rendered within the component which I want to have my video, and then other components will also have access to the context and be able to call functions such as usePlayVideo that based on the context's state, will either call videoElement.play() if this is the first time the video is being played, or dispatch the redux action to play the video programmatically otherwise
It is possible to store a ref into context! You need to create a context at first. Then you need to pass value to the context provider and create a ref object using useRef hook. After that, you pass the ref into the value.
Now, You have a ref object sharing between components under the context provider and if you want to retrieve or pass a new ref, you could use useContext hook to deal with it.
Here is the demo (codesandbox).
Here is the sample code.
import { createContext, useContext, useEffect, useRef, useState } from "react";
import "./styles.css";
const MyContext = createContext();
export const ContextStore = (props) => {
const ref = useRef();
return <MyContext.Provider value={ref}>{props.children}</MyContext.Provider>;
};
export default function App() {
return (
<>
<ContextStore>
<MyComponent />
<MyComponent2 />
</ContextStore>
</>
);
}
const MyComponent = () => {
const myContext = useContext(MyContext);
return (
<div className="App" ref={myContext}>
<h1>Hello MyComponent1</h1>
</div>
);
};
const MyComponent2 = () => {
const myContext = useContext(MyContext);
const [divRef, setDivRef] = useState();
useEffect(() => {
setDivRef(myContext);
}, [myContext]);
return (
<div className="App">
<h1>{divRef?.current && divRef.current.innerText}</h1>
</div>
);
};
You can use this approach:
VideoContext.js
import { createContext, createRef, useContext } from "react";
const VideoContext = createContext();
const videoRef = createRef();
export const VideoContextProvider = (props) => {
return (
<VideoContext.Provider value={videoRef}>
{props.children}
</VideoContext.Provider>
);
};
export const useVideoContext = () => useContext(VideoContext);
and App.js for example:
import { useState, useEffect } from "react";
import { useVideoContext, VideoContextProvider } from "./VideoContext";
const SomeComponent = () => {
const videoRef = useVideoContext();
return (
<div ref={videoRef}>
<h1>Hey</h1>
</div>
);
};
const SomeOtherComponent = () => {
const [ref, setRef] = useState();
const videoRef = useVideoContext();
useEffect(() => {
setRef(videoRef);
}, [videoRef]);
return (
<div>
<h1>{ref?.current?.innerText}</h1>
</div>
);
};
export default function App() {
return (
<>
<VideoContextProvider>
<SomeComponent />
</VideoContextProvider>
{/* ... */}
{/* Some other component in another part of the tree */}
<VideoContextProvider>
<SomeOtherComponent />
</VideoContextProvider>
</>
);
}
code sandbox
Why not? I'll say. Let's see if we can setup an example.
const fns = {}
const addDispatch = (name, fn) => { fns[name] = fn }
const dispatch = (name) => { fns[name] && fns[name]() }
const RefContext = createContext({ addDispatch, dispatch })
export default RefContext
const Child1 = () => {
const [video, dispatchVideo] = useState(...)
const { addDispatch } = useContext(RefContext)
useEffect(() => {
addDispatch('video', dispatchVideo)
}, [])
}
const Child2 = () => {
const { dispatch } = useContext(RefContext)
const onClick = () => { dispatch('video') }
...
}
The above two childs do not have to share the same ancestor.
I didn't use ref the way you wanted, but i think you can pass your ref to one of the function. This is a very basic idea. I haven't tested it yet. But seems it could work. A bit
I used this approach:
first I creacted the context and ContextProvider;
import React, { useRef } from "react";
export const ScrollContext = React.createContext();
const ScrollContextProvider = (props) => {
return (
<ScrollContext.Provider
value={{
productsRef: useRef(),
}}
>
{props.children}
</ScrollContext.Provider>
);
};
export default ScrollContextProvider;
then Added my provider in my index.js:
root.render(
<React.StrictMode>
<ScrollContextProvider>
<App />
</ScrollContextProvider>
</React.StrictMode>
);
after that I used my context where I needed it:
import React, { useContext } from "react";
import { ScrollContext } from "../../store/scroll-context";
const Products = () => {
const scrollCtx = useContext(ScrollContext);
return (
<section ref={scrollCtx.productsRef}>
// your code...
</section>
);
};
In my case I wanted to to scroll to the above component clicking a button from a different component:
import React, { useContext } from "react";
import { ScrollContext } from "../../store/scroll-context";
function Header() {
const scrollCtx = useContext(ScrollContext);
const scrollTo = () => {
setTimeout(() => {
scrollCtx.productsRef.current.scrollIntoView({ behavior: "smooth" });
}, 0);
};
return (
<header>
//your code ...
<button alt="A table with chair" onClick={scrollTo}>Order Now<button />
</header>
);
}
No. It's not possible to use Ref on context api. React ref is considered to be used on rendering element.
What you're looking for is to forward the ref, so that you can consume them wherever you want.

Strange behavior of useState() method of react hook while fetching data from axios

I am using axios library to fetch data from a json file through json-server.
When I am loading and using the response object in a single component it is working perfectly. But when I am passing this response object to child component from parent component it is not loading the data. Also not receiving any errors, can someone please help me to understand the difference and what is wrong with my approach?
//Scenario-1 : working perfectly fine:
import React, { useState, useEffect } from 'react';
import Display from './Display';
import Note from './note'
import axios from 'axios';
const App = () => {
const [notes, setNotes] = useState([])
const hook = () => {
axios.get('http://localhost:3001/notes')
.then(response => {
setNotes(response.data)
})
}
useEffect(hook, [])
return (
<div>
{notes.map(n => <Note key={n.id} note={n} />)}
</div>
)
}
export default App;
//Scenario-2 : Not working as expected, also no errors.
const Display = (props) => {
//Receiving data here, can see the values in console.
console.log('inside display, props.notex: ', props.notex);
const [notes, setNotes] = useState(props.notex);
//Blank object, why useState() method is not setting the value of "notes" from "props.notex".
console.log('inside display, notes: ', notes);
const generateRows = () => {
console.log('generateRows: ', notes)
return (
notes.map(n => <Note key={n.id} note={n} />)
)
}
return (
<div>
<ul>
{generateRows()}
</ul>
</div>
)
}
const App = () => {
const [notes, setNotes] = useState([])
const hook = () => {
axios.get('http://localhost:3001/notes')
.then(response => {
setNotes(response.data)
})
}
useEffect(hook, [])
return (
<div>
<Display notex={notes} />
</div>
)
}
export default App;
My guess is that useState is asynchronous, same as setState in Class components. Due to its async nature, you are not able to log anything - the log gets executed before the useState actually does anything.
If you really want to do it this way, you could initialize the value of the useState as an empty array and set up a useEffect hook, with the props.notex in your dependency array, something like this:
useEffect(() => {
if (props.notex) setNotes(props.notex)
}, [props.notex])
And then in the return
return (
<div>
<ul>
{notes.length && generateRows()}
</ul>
</div>
)
But you could just pass the props down from the parent to child without setting the state in the child component.
Hope this helps!