I am using the react-d3-tree to render a tree chart. I want to use create a custom node by passing a function to the renderCustomNodeElement prop, but somehow it seems this prop is not accepting the value passed.
Here is the code:
import React, { useState, useEffect } from 'react';
import Tree from "react-d3-tree";
import { useCenteredTree } from "./helpers";
import "./Tree.css";
import Cookies from 'js-cookie';
import { load_tree } from '../actions/tree';
import { connect } from 'react-redux';
const mapStateToProps = state => ({
tree: state.tree
});
const renderRectSvgNode = ({ nodeDatum, toggleNode }) => (
<g>
<rect width="20" height="20" x="-10" onClick={toggleNode} />
<text fill="black" strokeWidth="1" x="20">
{nodeDatum.name}
</text>
{nodeDatum.attributes?.department && (
<text fill="black" x="20" dy="20" strokeWidth="1">
Department: {nodeDatum.attributes?.department}
</text>
)}
</g>
);
export function TreeGraph({tree}) {
const containerStyles = {
width: "100vw",
height: "100vh"
};
const [translate, containerRef] = useCenteredTree();
const nodeSize = { x: 100, y: 100 };
const foreignObjectProps = { width: nodeSize.x, height: nodeSize.y, x: -100 };
const makeTree = (familyData, CustomNodeElementFunction, nodeSize) => {
console.log(CustomNodeElementFunction);
return (
<Tree
data={familyData}
rootNodeClassName="node__root"
branchNodeClassName="node__branch"
leafNodeClassName="node__leaf"
translate={translate}
nodeSize={nodeSize}
renderCustomNodeElement={CustomNodeElementFunction}
pathFunc="step"
initialDepth="1"
orientation="vertical"
/>
);}
return (
<div dir="ltr" style={containerStyles} ref={containerRef}>
{ typeof tree.payload !== 'undefined' && tree.payload.length > 0
? makeTree(tree.payload, renderRectSvgNode, nodeSize)
: makeTree({}, renderRectSvgNode, nodeSize) }
</div>
);
}
export default connect(mapStateToProps)(TreeGraph);
Can someone see what is missing?
Related
So here is my code:
VideoCall.jsx:
import React, { useState, useEffect } from "react";
import { config, useClient, useMicrophoneAndCameraTracks, channelName} from "./settings.js";
import { Grid } from "#material-ui/core";
import Video from "./Video.jsx";
import Controls from "./Controls";
export default function VideoCall(props) {
const { setInCall } = props;
const [users, setUsers] = useState([]);
const [start, setStart] = useState(false);
const client = useClient();
const { ready, tracks } = useMicrophoneAndCameraTracks();
useEffect(() => {
let init = async (name) => {
client.on("user-published", async (user, mediaType) => {
await client.subscribe(user, mediaType);
if (mediaType === "video") {
setUsers((prevUsers) => {
return [...prevUsers, user];
});
}
if (mediaType === "audio") {
user.audioTrack.play();
}
});
client.on("user-unpublished", (user, mediaType) => {
if (mediaType === "audio") {
if (user.audioTrack) user.audioTrack.stop();
}
if (mediaType === "video") {
setUsers((prevUsers) => {
return prevUsers.filter((User) => User.uid !== user.uid);
});
}
});
client.on("user-left", (user) => {
setUsers((prevUsers) => {
return prevUsers.filter((User) => User.uid !== user.uid);
});
});
try {
await client.join(config.appId, name, config.token, null);
} catch (error) {
console.log("error");
}
debugger;
if (tracks) await client.publish([tracks[0], tracks[1]]);
setStart(true);
};
if (ready && tracks) {
try {
init(channelName);
} catch (error) {
console.log(error);
}
}
}, [channelName, client, ready, tracks]);
return (
<Grid container direction="column" style={{ height: "100%" }}>
<Grid item style={{ height: "5%" }}>
{ready && tracks && (
<Controls tracks={tracks} setStart={setStart} setInCall={setInCall} />
)}
</Grid>
<Grid item style={{ height: "95%" }}>
{start && tracks && <Video tracks={tracks} users={users} />}
</Grid>
</Grid>
);
}
Video.jsx:
import { AgoraVideoPlayer } from "agora-rtc-react";
import { Grid } from "#material-ui/core";
import React, { useState, useEffect } from "react";
export default function Video(props) {
const { users, tracks } = props;
const [gridSpacing, setGridSpacing] = useState(12);
useEffect(() => {
setGridSpacing(Math.max(Math.floor(12 / (users.length + 1)), 4));
}, [users, tracks]);
return (
<Grid container style={{ height: "100%" }}>
<Grid item xs={gridSpacing}>
<AgoraVideoPlayer
videoTrack={tracks[1]}
style={{ height: "100%", width: "100%" }}
/>
</Grid>
{users.length > 0 &&
users.map((user) => {
if (user.videoTrack) {
return (
<Grid item xs={gridSpacing}>
<AgoraVideoPlayer
videoTrack={user.videoTrack}
key={user.uid}
style={{ height: "100%", width: "100%" }}
/>
</Grid>
);
} else return null;
})}
</Grid>
);
}
Controls.jsx:
import React, { useState } from "react";
import { useClient } from "./settings";
import { Grid, Button } from "#material-ui/core";
import ExitToAppIcon from "#material-ui/icons/ExitToApp";
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faMicrophone, faMicrophoneSlash, faVideoSlash, faVideo } from "#fortawesome/free-solid-svg-icons";
import { useTranslation } from 'react-i18next';
export default function Controls(props) {
const {t} = useTranslation();
const client = useClient();
const { tracks, setStart, setInCall } = props;
const [trackState, setTrackState] = useState({ video: true, audio: true });
const mute = async (type) => {
if (type === "audio") {
await tracks[0].setEnabled(!trackState.audio);
setTrackState((ps) => {
return { ...ps, audio: !ps.audio };
});
} else if (type === "video") {
await tracks[1].setEnabled(!trackState.video);
setTrackState((ps) => {
return { ...ps, video: !ps.video };
});
}
};
const leaveChannel = async () => {
await client.leave();
client.removeAllListeners();
tracks[0].close();
tracks[1].close();
setStart(false);
setInCall(false);
};
return (
<Grid container spacing={2} alignItems="center">
<Grid item>
<Button variant="contained" color={trackState.audio ? "primary" : "secondary"} onClick={() => mute("audio")} >
{trackState.audio ? <FontAwesomeIcon icon={faMicrophone} /> : <FontAwesomeIcon icon={faMicrophoneSlash} />}
</Button>
</Grid>
<Grid item>
<Button variant="contained" color={trackState.video ? "primary" : "secondary"} onClick={() => mute("video")} >
{trackState.video ? <FontAwesomeIcon icon={faVideo} /> : <FontAwesomeIcon icon={faVideoSlash} />}
</Button>
</Grid>
<Grid item>
<Button variant="contained" color="default" onClick={() => leaveChannel()} >
{t('agora.leave')}
<ExitToAppIcon />
</Button>
</Grid>
</Grid>
);
}
settings.js:
import {createClient, createMicrophoneAndCameraTracks} from 'agora-rtc-react';
export const config = {mode: 'rtc', codec: "vp8", appId: process.env.AGORA_APP_ID, token : process.env.AGORA_TOKEN};
export const useClient = createClient(config);
export const useMicrophoneAndCameraTracks = createMicrophoneAndCameraTracks();
export const channelName = process.env.AGORA_CHANNEL_NAME;
AgoraDemo.jsx:
import React, {useState} from "react";
import {Form, Modal, Button,Row, Col} from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import VideoCall from "./agora/VideoCall";
const AgoraDemo = ({subjects}) => {
const {t} = useTranslation();
const [subjectSelected, setSubjectSelected] = useState(null);
const [inCall, setInCall] = useState(false);
return (
<Modal.Dialog size='xl'>
<Modal.Header style={{ display: 'flex', justifyContent: 'center', alignItems: 'center'}}>
<Modal.Title>
{t('agora.headline')}
</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className="App" style={{ height: "100%" }}>
{subjectSelected && inCall ? (
<VideoCall setInCall={setInCall} />
) : (
<Button onClick={() => setInCall(true)}>
{t('agora.join call')}
</Button>
)}
</div>
</Modal.Body>
<Modal.Footer/>
</Modal.Dialog>
)
}
export default AgoraDemo;
I don't know much plain javascript so I don't understand why this await does not pass first before proceding to the next line of code and how I should fix it. This is the error:
×
Unhandled Rejection (AgoraRTCException): AgoraRTCError INVALID_OPERATION: Can't publish stream, haven't joined yet!
▶ 2 stack frames were collapsed.
async init
C:/Users/User/Desktop/Agora/src/agora/VideoCall.jsx:53
50 |
51 | debugger;
52 |
53 | if (tracks) await client.publish([tracks[0], tracks[1]]);
| ^ 54 | setStart(true);
55 | };
56 |
The implementation was working. The problem was with the token. It turned out it expires (but didn't get afteh how long exactly) and then you get the given error. For me the solution was to obtain a token for every video call. This way I don't need to renew it manually every time it expires :)
I have been created a react native app,on first launch of the app it go to the route appjs->onboardingscreen->loginscreen->tabscreen and in tab screen there are 3 tabs on the last tab profile there is the function that clear context and storage to log out but on preforming that it crashes.that error shows that it previous stack context value is null but on second launch the app work perfect on the route loginscreen->tabscrenn afterwords its log out perfect
import React, { useContext } from "react";
import { View, StyleSheet, Text, SafeAreaView, ScrollView, TouchableOpacity } from "react-native";
import HeaderLayout from "../../Components/HeaderLayout";
import Statusbar from "../../Components/Statusbar";
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
import Profile_Detail_Tab from "../../Components/Profile_Detail_Tab";
import AuthContext from "../../auth/context";
import storage from "../../auth/storage";
import { useBottomTabBarHeight } from "#react-navigation/bottom-tabs";
function Profile({ navigation }) {
const tabheight = useBottomTabBarHeight();
const { userData, setUserData } = useContext(AuthContext);
const key = "#storage_Key";
console.log("profile");
const handleLogOut = () => {
setUserData(null);
storage.removeData(key);
console.log("logout");
};
return (
<SafeAreaView>
<Statusbar />
<HeaderLayout title={"Profile"} />
<ScrollView>
<View style={{ backgroundColor: "#ffffff", paddingBottom: tabheight, marginBottom: 10 }}>
<View
style={{
width: "100%",
height: 100,
backgroundColor: "#04213c",
borderBottomEndRadius: 50,
borderBottomStartRadius: 50,
}}
/>
<View style={styles.container}>
<MaterialCommunityIcons name="account" size={140} color="white" />
</View>
<Text style={{ marginLeft: 30, padding: 10, fontSize: 20, fontWeight: "bold" }}>
{userData.detail.first_name + " " + userData.detail.last_name}
</Text>
<View>
<Profile_Detail_Tab text_color="#000000" color="#ffffff" Title="email" value={userData.detail.email} />
<Profile_Detail_Tab text_color="#000000" color="#ffffff" Title="phone" value={userData.detail.mobile} />
<Profile_Detail_Tab
text_color="#000000"
color="#ffffff"
Title="map-marker-outline"
value={userData.detail.work_station}
/>
<Profile_Detail_Tab
text_color="#000000"
color="#ffffff"
Title="account-hard-hat"
value={userData.detail.role}
/>
<Profile_Detail_Tab
text_color="#000000"
color="#ffffff"
Title="message-reply-text"
value={userData.detail.designation}
/>
<TouchableOpacity onPress={handleLogOut}>
<Profile_Detail_Tab text_color="#ffffff" color="#04213c" Title="logout" value="Logout" />
</TouchableOpacity>
</View>
</View>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
backgroundColor: "#d1c9d3",
borderRadius: 15,
width: 140,
height: 140,
borderRadius: 70,
marginLeft: 30,
marginTop: -80,
},
});
export default Profile;
[
import { NavigationContainer } from "#react-navigation/native";
import React, { useEffect, useState } from "react";
import Tabs from "./src/Navigation/Tabs";
import StackNavigation from "./src/Navigation/StackNavigation";
import AuthContext from "./src/auth/context";
import storage from "./src/auth/storage";
import AppLoading from "expo-app-loading";
import AsyncStorage from "#react-native-community/async-storage";
import AuthNavigation from "./src/Navigation/AuthNavigation";
import { ActivityIndicator, Text, View } from "react-native";
function App() {
const [userData, setUserData] = useState();
const key = "#storage_Key";
const [isReady, setIsReady] = useState(false);
const [isFirstLaunch, setIsFirstLaunch] = useState(null);
useEffect(() => {
checkdata();
}, []);
console.log("App js");
const checkdata = async () => {
const appData = await AsyncStorage.getItem("isAppFirstLaunched");
if (appData == null) {
setIsFirstLaunch(true);
AsyncStorage.setItem("isAppFirstLaunched", "false");
} else {
setIsFirstLaunch(false);
}
};
// console.log(isFirstLaunch);
const getToken = async () => {
const data = await storage.getData(key);
if (data == null) return;
setUserData(data);
console.log(data);
};
console.log(userData);
if (!isReady) return <AppLoading startAsync={getToken} onFinish={() => setIsReady(true)} onError={console.warn} />;
if (isFirstLaunch == null) {
return (
<View style={{ flex: 1 }}>
<ActivityIndicator size="large" />
</View>
);
} else if (isFirstLaunch == true) {
return (
<AuthContext.Provider value={{ userData, setUserData }}>
<NavigationContainer>
<StackNavigation />
</NavigationContainer>
</AuthContext.Provider>
);
} else {
return (
<AuthContext.Provider value={{ userData, setUserData }}>
<NavigationContainer>{userData ? <Tabs /> : <AuthNavigation />}</NavigationContainer>
</AuthContext.Provider>
);
}
}
export default App;
]1
enter image description here
import { NavigationContainer } from "#react-navigation/native";
import React, { useEffect, useState } from "react";
import Tabs from "./src/Navigation/Tabs";
import StackNavigation from "./src/Navigation/StackNavigation";
import AuthContext from "./src/auth/context";
import storage from "./src/auth/storage";
import AppLoading from "expo-app-loading";
import AsyncStorage from "#react-native-community/async-storage";
import AuthNavigation from "./src/Navigation/AuthNavigation";
import { ActivityIndicator, Text, View } from "react-native";
function App() {
const [userData, setUserData] = useState();
const key = "#storage_Key";
const [isReady, setIsReady] = useState(false);
const [isFirstLaunch, setIsFirstLaunch] = useState(null);
useEffect(() => {
checkdata();
}, []);
console.log("App js");
const checkdata = async () => {
const appData = await AsyncStorage.getItem("isAppFirstLaunched");
if (appData == null) {
setIsFirstLaunch(true);
AsyncStorage.setItem("isAppFirstLaunched", "false");
} else {
setIsFirstLaunch(false);
}
};
// console.log(isFirstLaunch);
const getToken = async () => {
const data = await storage.getData(key);
if (data == null) return;
setUserData(data);
console.log(data);
};
console.log(userData);
if (!isReady) return <AppLoading startAsync={getToken} onFinish={() => setIsReady(true)} onError={console.warn} />;
if (isFirstLaunch == null) {
return (
<View style={{ flex: 1 }}>
<ActivityIndicator size="large" />
</View>
);
} else if (isFirstLaunch == true) {
return (
<AuthContext.Provider value={{ userData, setUserData }}>
<NavigationContainer>
<StackNavigation />
</NavigationContainer>
</AuthContext.Provider>
);
} else {
return (
<AuthContext.Provider value={{ userData, setUserData }}>
<NavigationContainer>{userData ? <Tabs /> : <AuthNavigation />}</NavigationContainer>
</AuthContext.Provider>
);
}
}
export default App;
use userData?.detail instead of userData.detail coz its getting undefined where you shown error in image
Here's the function-
const setLoading = (value) => {
const messages = dashboards.data.message.filter((item) => {
const title = item.dashboardTitle || item.dashboardName;
return title.toLowerCase().startsWith(value.toLowerCase());
});
setFiltered(messages);
console.log(filtered);
};
I want to display the variable 'messages' separately in my app, how would I do that? 'messages' variable needs to be displayed within the default react native 'Text' component. I have written down 'messages' below within Text component but currently it's not displaying anything (since it is within function) -
import React, { useState, useEffect, useReducer } from 'react';
import { View, Text, StyleSheet, FlatList, ActivityIndicator, Keyboard} from 'react-native';
import { Searchbar } from 'react-native-paper';
import { theme } from '../theme';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import { TouchableOpacity } from 'react-native-gesture-handler';
import { apiStateReducer } from '../reducers/ApiStateReducer';
import CognitensorEndpoints from '../services/network/CognitensorEndpoints';
import DefaultView from '../components/default/DefaultView';
import DashboardListCard from '../components/DashboardListCard';
const AppHeader = ({
scene,
previous,
navigation,
searchIconVisible = false,
}) => {
const [dashboards, dispatchDashboards] = useReducer(apiStateReducer, {
data: [],
isLoading: true,
isError: false,
});
const [filtered, setFiltered] = useState([]);
const setLoading = (value) => {
const messages = dashboards.data.message.filter((item) => {
const title = item.dashboardTitle || item.dashboardName;
return title.toLowerCase().startsWith(value.toLowerCase());
});
setFiltered(messages);
console.log(filtered);
};
const dropShadowStyle = styles.dropShadow;
const toggleSearchVisibility = () => {
navigation.navigate('Search');
};
useEffect(() => {
CognitensorEndpoints.getDashboardList({
dispatchReducer: dispatchDashboards,
});
}, []);
return (
<>
<View style={styles.header}>
<View style={styles.headerLeftIcon}>
<TouchableOpacity onPress={navigation.pop}>
{previous ? (
<MaterialIcons
name="chevron-left"
size={24}
style={styles.visible}
/>
) : (
<MaterialIcons
name="chevron-left"
size={24}
style={styles.invisible}
/>
)}
</TouchableOpacity>
</View>
<Text style={styles.headerText}>
{messages}
</Text>
<View style={styles.headerRightIconContainer}>
{searchIconVisible ? (
<TouchableOpacity
style={[styles.headerRightIcon, dropShadowStyle]}
onPress={toggleSearchVisibility}>
<MaterialIcons name="search" size={24} style={styles.visible} />
</TouchableOpacity>
) : (
<View style={styles.invisible} />
)}
</View>
</View>
</>
);
};
If your messages variable is an array you can map it
{messages.map((message, key)=>(
<Text style={styles.headerText}>
{message.dashboardName}
</Text>
))}
Since your messages variable is stored in 'filtered' state, you can map it by doing this:
{filtered.map((item, index) => <Text key={index}>{item.dashboardName}<Text>)}
OK, so I just started to play with react and redux, and have encountered problem. I have button which calls API, and receives info about some cars. It works like this:
on button click it dispatches function getCars(),
this in turn dispatches pendingAction, then fetches info, then dispatches successAction or errorAction. (I'll show all code bellow).
My problem is:
while loading new info it changes states to pending, and then re-renders pictures, even though they have same src. I want, to avoid re-render as, it makes pictures flash to white for a second.
I have my app set up like this:
//index.js
import App from './App';
import * as serviceWorker from './serviceWorker';
import { applyMiddleware, createStore, compose } from 'redux';
import { Provider } from 'react-redux';
import Reducers from './/Reducers';
import thunk from 'redux-thunk';
const middlewares = [thunk];
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(Reducers, composeEnhancers(
applyMiddleware(...middlewares)
));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: somelink
serviceWorker.unregister();
Then my app.js
//App.js
import React from 'react';
import 'rsuite/dist/styles/rsuite-default.css';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, login, logout } from './/Actions/TestingActions';
import GetCars from './/API/Cars/GetCars';
import { Button } from 'rsuite';
import CarView from './Components/CarTestView/CarView'
//import './index.css';
function App() {
const counter = useSelector(state => state.count)
const logged = useSelector(state => state.loggedin)
const dispatch = useDispatch()
return (
<div className="App">
//
// I hidden some unrelated code here ...
//
<Button onClick={() => dispatch(GetCars())}>Getcars</Button>
<CarView />
</div>
);
}
export default App;
GetCars...
//GetCars.js
import { apiCarsError, apiCarsSuccess, apiCarsPending } from '../../Actions/TestingActions';
export function GetCars() {
return dispatch => {
dispatch(apiCarsPending());
fetch('https://localhost:44342/API/GetRandomCar')
.then(res => {
res.json().then(res => {
if (res.error) {
throw (res.error);
}
dispatch(apiCarsSuccess(res));
return res;
})
.catch(error => {
dispatch(apiCarsError(error));
})
});
}
}
export default GetCars;
finally CarView.
import React from 'react';
import { connect, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux';
import { GetCars } from '../../API//Cars//GetCars';
import { getCars, getCarsPending, getCarsError } from '../../Reducers//TestingReducer';
import { Loader, Placeholder, Panel, /*PanelGroup*/ } from 'rsuite';
//import { CSSTransition, TransitionGroup } from 'react-transition-group';
const { Paragraph } = Placeholder;
function CarView() {
const pending = useSelector(state => state.API.pending)
//const error = useSelector(state => state.API.error)
const cars = useSelector(state => state.API.cars)
if (pending && cars.length === 0) return (
<div>
{console.log("Update is nulio")}
<Loader backdrop content="loading..." vertical />
<Paragraph rows={8}></Paragraph>
</div>
)
if (pending) return (
<div>
{console.log("Update pending")}
<Loader center content="keiciam metus" />
<div>
<Carvaizdas cars={cars} updatePicture={false} />
</div>
</div>
)
if (cars.length === 0) return (<div>{console.log("tuscia")}</div>)
return (
<div>
{console.log("uzkrauta || new info")}
<div>
<Carvaizdas cars={cars} updatePicture={true} />
</div>
</div>
)
}
class Carvaizdas extends React.PureComponent {
shouldComponentUpdate() {
console.log("Should render ?");
console.log(this.props.updatePicture);
return this.props.updatePicture;
}
render() {
console.log("render cars");
return (
<>
<h1>Masinos</h1>
{this.props.cars.map(car => <CarKorta car={car}/>)}
</>
);
}
}
class CarKorta extends React.PureComponent {
render() {
return (
<Panel shaded bordered bodyFill style={{ display: 'inline-block', width: 240, margin: 10 }}>
<div style={{ height: 150, width: 240, display: 'flex', alignItems: 'center', justifyContent: 'center', paddingTop: 10 }}>
<div style={{ height: 'auto', width: 220 }}>
<img src={this.props.car.picture} /*height="240"*/ style={{ maxHeight: 150, height: 'auto', width: 220, borderRadius: 5, boxShadow: "1px 1px 2px #666" }} />
</div>
</div>
<Panel header={this.props.car.make}>
<p>
Year: {this.props.car.year}
<br />
Model: {this.props.car.model}
</p>
</Panel>
</Panel>
);
}
}
const mapStateToProps = state => ({
error: getCarsError(state),
cars: getCars(state),
pending: getCarsPending(state)
})
const mapDispatchToProps = dispatch => bindActionCreators({
CarView: GetCars()
}, dispatch)
export default connect(
mapStateToProps,
mapDispatchToProps
)(CarView);
Thanks for your help.
The problem is in using 2 instances of component Carvaizdas under a different condition. This makes no sence for shouldComponentUpdate hook which is specific PER INSTANCE.
if (pending)
return (
<div>
{console.log("Update pending")}
<Loader center content="keiciam metus" />
<div>
<Carvaizdas cars={cars} updatePicture={false} /> {/** first component instance */}
</div>
</div>
);
if (cars.length === 0) return <div>{console.log("tuscia")}</div>;
return (
<div>
{console.log("uzkrauta || new info")}
<div>
<Carvaizdas cars={cars} updatePicture={true} /> {/** second component instance */}
</div>
</div>
);
In order to shouldComponentUpdate to work there should be only single instance
return (
<div>
{console.log("uzkrauta || new info")}
<div>
<Carvaizdas cars={cars} />
</div>
</div>
);
And in this component using shouldComponentUpdate makes no sense too
class Carvaizdas extends React.PureComponent {
render() {
console.log("render cars");
return (
<>
<h1>Masinos</h1>
{this.props.cars.map(car => <CarKorta car={car}/>)}
</>
);
}
}
It only makes sense for CarKorta. You should remove shouldComponentUpdate from Carvaizdas and add it to CarKorta. Also you will have to store the previous picture in CarKorta state in order to be able to compare it with next picture. For this you have to use getDerivedStateFromProps
class CarKorta extends React.PureComponent {
state = {
car: null,
};
shouldComponentUpdate(nextProps) {
return !this.state.car || this.state.car.picture !== nextProps.car.picture;
}
static getDerivedStateFromProps(nextProps, prevState) {
return {
car: { ...nextProps.car },
};
}
render() {
return (
<Panel
shaded
bordered
bodyFill
style={{ display: "inline-block", width: 240, margin: 10 }}
>
<img
src={this.state.car.picture}
/*height="240"*/ style={{
maxHeight: 150,
height: "auto",
width: 220,
borderRadius: 5,
boxShadow: "1px 1px 2px #666",
}}
/>
</Panel>
);
}
}
I change CarView like this and it works now.
import React from 'react';
import { connect, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux';
import { GetCars } from '../../API//Cars//GetCars';
import { getCars, getCarsPending, getCarsError } from '../../Reducers//TestingReducer';
import { Loader, Placeholder, Panel, /*PanelGroup*/ } from 'rsuite';
//import { CSSTransition, TransitionGroup } from 'react-transition-group';
const { Paragraph } = Placeholder;
function CarView() {
const pending = useSelector(state => state.API.pending)
//const error = useSelector(state => state.API.error)
const cars = useSelector(state => state.API.cars)
if (pending && cars.length === 0) return (
<div>
{console.log("Update is nulio")}
<Loader backdrop content="loading..." vertical />
<Paragraph rows={8}></Paragraph>
</div>
)
if (cars.length === 0) return (<div>{console.log("tuscia")}</div>)
return (
<div>
{console.log("uzkrauta || new info")}
{pending ? <Loader center content="keiciam metus" />: <></>}
<div>
<Carvaizdas cars={cars} />
</div>
</div>
)
}
class Carvaizdas extends React.PureComponent {
render() {
console.log("render cars");
return (
<>
<h1>Masinos</h1>
{this.props.cars.map(car => <CarKorta car={car}/>)}
</>
);
}
}
class CarKorta extends React.PureComponent {
render() {
return (
<Panel shaded bordered bodyFill style={{ display: 'inline-block', width: 240, margin: 10 }}>
<div style={{ height: 150, width: 240, display: 'flex', alignItems: 'center', justifyContent: 'center', paddingTop: 10 }}>
<div style={{ height: 'auto', width: 220 }}>
<img src={this.props.car.picture} /*height="240"*/ style={{ maxHeight: 150, height: 'auto', width: 220, borderRadius: 5, boxShadow: "1px 1px 2px #666" }} />
</div>
</div>
<Panel header={this.props.car.make}>
<p>
Year: {this.props.car.year}
<br />
Model: {this.props.car.model}
</p>
</Panel>
</Panel>
);
}
}
const mapStateToProps = state => ({
error: getCarsError(state),
cars: getCars(state),
pending: getCarsPending(state)
})
const mapDispatchToProps = dispatch => bindActionCreators({
CarView: GetCars()
}, dispatch)
export default connect(
mapStateToProps,
mapDispatchToProps
)(CarView);
I have a project where i can have n-number of input field. After when i add 200 items, it starts lagging after that. Demo : https://testcreate.vivekneel.now.sh/create (To test: Focus last input and try pressing enter button to create new input)
Source Code. : https://github.com/VivekNeel/Create-Test
This is my main container :
import React, { useState } from "react";
import CreateTerms from "./CreateTerms";
import { initTerms, contructTermObject } from "./utils";
import { Container } from "#material-ui/core/";
const CreateStudySetContainer = () => {
const [terms, setTerms] = useState(initTerms);
const [inputIdToFocus, setInputIdToFocus] = useState(null);
const handleCreateTerm = () => {
const newTerm = contructTermObject(terms.length + 1, 2);
const newTerms = [...terms, newTerm];
setInputIdToFocus(terms.length + 1);
setTerms(newTerms);
};
console.log("....rendering");
return (
<Container maxWidth={"md"}>
<CreateTerms
terms={terms}
inputIdToFocus={inputIdToFocus}
createTerm={handleCreateTerm}
/>
;
</Container>
);
};
export default CreateStudySetContainer;
This the CreateTerms code :
import React from "react";
import CreateFacts from "./CreateFacts";
import { withStyles, Card } from "#material-ui/core/";
import ContentItemRow from "./ContentItemRow";
const styles = () => ({
card: {
marginBottom: 16,
},
});
const CreateTerms = (props) => {
const { terms, classes, createTerm, inputIdToFocus } = props;
return (
<div className={classes.container}>
{terms.map(({ node: { term } }, index) => {
return (
<Card key={term.id} className={classes.card}>
<p>{index}</p>
<ContentItemRow
autoFocus={term.id === inputIdToFocus}
createTerm={createTerm}
term={term}
/>
;
</Card>
);
})}
</div>
);
};
export default withStyles(styles)(CreateTerms);
This is ContentItemRow :
import React from "react";
import CreateFacts from "./CreateFacts";
import { withStyles } from "#material-ui/core/";
import ContentEditor from "./ContentEditor";
const styles = {
container: {
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "flex-start",
},
term: {
marginRight: 16,
flex: 1,
},
facts: {
flex: 1,
},
};
const ContentItemRow = (props) => {
const { term, classes, createTerm, autoFocus } = props;
return (
<div className={classes.container}>
<div className={classes.term}>
<ContentEditor
autoFocus={autoFocus}
createTerm={createTerm}
placeholder={"New term"}
/>
</div>
<div className={classes.facts}>
{term.facts.map(({ fact }) => {
return (
<ContentEditor
key={fact.id}
createTerm={createTerm}
placeholder={"New fact"}
/>
);
})}
</div>
</div>
);
};
export default withStyles(styles)(ContentItemRow);
This is ContentEditor :
import React from "react";
import { TextField } from "#material-ui/core/";
const ContentEditor = (props) => {
const { placeholder, createTerm, autoFocus } = props;
const handleOnKeyDown = (event) => {
const { keyCode } = event;
if (keyCode === 13) {
createTerm();
}
};
return (
<TextField
onKeyDown={handleOnKeyDown}
fullWidth
autoFocus={autoFocus}
placeholder={placeholder}
/>
);
};
export default ContentEditor;
When debugging, I noticed that dom updates only the last div which gets added. I don't know where the lagging is coming from.
Not sure if this will work but you can try the following:
const ContentItemRow = React.memo(function ContentItemRow (props) => {
And to prevent re creating the create term handler:
const handleCreateTerm = useCallback(() => {
setTerms((terms) => {
const newTerm = contructTermObject(
terms.length + 1,
2
);
const newTerms = [...terms, newTerm];
setInputIdToFocus(terms.length + 1);
return newTerms;
});
}, []);